iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Vibe Coding

夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略系列 第 28

Day28|查得到就夠了嗎?從 Index Design 看懂 Query 怎麼決定索引怎麼加

  • 分享至 

  • xImage
  •  

昨天,我們終於把網站從本機送到了真正的執行環境。

網站真的跑起來之後,除了能不能正常運作,效能也慢慢進入需要注意的範圍。

Day6 講資料庫約束時,我們曾經用一句話把 Constraint 和 Index 分開:

約束(Constraint)管資料對不對;索引(Index)影響資料查得快不快。

當時只先看了 Index 在做什麼,至於哪些欄位值得加、怎麼設計,特地留到效能優化再回來拆。

今天,就接著把這一塊打開來看。


Index 是什麼?先把它想成一本書的目錄

如果用最白話的方式理解,我覺得 Index 很像一本書的目錄或索引頁

假設有一本 1000 頁的書,我想找「巧克力甘納許」。

沒有目錄時,只能從大量內容裡慢慢找。

沒有目錄
整本書 → 一頁一頁找 → 找到內容

有目錄
目錄 → 先找到範圍 → 翻到內容

Database 也有類似的問題。

假設現在有一張:

orders

裡面放了很多訂單。

我要找某個使用者的所有訂單:

SELECT *
FROM orders
WHERE user_id = 123;

如果沒有適合這支 Query 的 Index,Database 可能需要檢查大量 Row(資料列),才能找出符合 user_id = 123 的資料。

這時可以替 user_id 建立 Index:

CREATE INDEX idx_orders_user_id
ON orders(user_id);

Database 就多了一套額外的查找結構,可以先縮小搜尋範圍,再找到需要的資料。

所以 Index 可以先理解成:

替資料建立一套額外的查找結構,讓 Database 在適合的 Query 裡,更有效率地找到資料。

不過有 Index,不代表 Database 每次都一定會用它。

到底要不要用、怎麼用,Database 還會自己判斷。

這件事我們留到明天直接看 EXPLAIN ANALYZE

今天先從 Index Design 最直接的一步開始:

Index 到底該加在哪裡?


Index 不是看哪個欄位重要,而是看 Query 怎麼查

假設 orders 長這樣:

orders

id
user_id
status
created_at
total

看到一張 Table,很容易先盯著欄位問:

哪個欄位比較重要?哪個要加 Index?

但 Index 是拿來幫 Query 找資料的。

所以真正要先看的,是系統平常怎麼查:

WHERE user_id = ?

或:

WHERE user_id = ?
AND status = ?

又或是:

WHERE created_at >= ?

這些反覆出現的查詢方式,就是 Query Pattern(查詢模式)

例如系統經常執行:

SELECT *
FROM orders
WHERE user_id = 123;

user_id 才是一個值得開始評估 Index 的欄位。

不是因為:

user_id 看起來很重要。

而是因為:

系統真的經常用 user_id 找訂單。

所以設計 Index 的第一步,不是先翻 Schema。

而是先看:

Database 平常到底在回答哪些 Query?


出現在 WHERE 裡,也不代表全部都要加

那是不是只要常出現在 WHERE 裡,就全部加 Index?

也不是。

假設 orders 有:

user_id
status

user_id 可能有幾十萬種不同值,但 status 可能只有四種:

pending
paid
completed
cancelled

而且這四種狀態不一定平均分布。

假設整張 Table 有 100 萬筆訂單,其中 90% 都已經是 completed

這時查:

WHERE user_id = 123

可能一下就從 100 萬筆縮到某個使用者的幾十筆。

但查:

WHERE status = 'completed'

卻還會留下大約 90 萬筆資料。

兩個條件都有篩選資料,但縮小範圍的能力差很多。

這就是 Selectivity(選擇性) 可以幫我們理解的事情。

簡單來說,就是:

一個查詢條件能把資料範圍縮小多少。

status = 'completed' 這種篩完還剩下大半張 Table 的條件,單獨替它建立 Index 就不一定划算。

所以:

出現在 WHERE 裡,只代表它值得被觀察,不代表一定要加 Index。

實際還要一起看資料分布和 Query 怎麼使用它。


一次查兩個欄位,就開始考慮 Composite Index

現在把 Query 改成:

SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'completed';

這支 Query 經常一起使用:

user_id + status

如果這就是系統常見的 Query Pattern,就可以開始考慮 Composite Index(複合索引)

原本單欄 Index:

CREATE INDEX idx_orders_user_id
ON orders(user_id);

只有一個欄位。

Composite Index 則可以把多個欄位放在同一個 Index 裡:

CREATE INDEX idx_orders_user_status
ON orders(user_id, status);

也就是:

Single-column Index
(user_id)

Composite Index
(user_id, status)

但這裡有一個很容易誤會的地方。

(user_id, status) 並不等於:

user_id 有一份 Index
+
status 也有一份 Index

我們今天先看 PostgreSQL 最常見的 B-tree Composite Index,這時欄位順序就會影響它怎麼被使用。


Composite Index 為什麼不能隨便排?

假設我們建立:

CREATE INDEX idx_orders_user_status
ON orders(user_id, status);

也就是:

(user_id, status)

可以先把它想成一本按照:

姓氏 → 名字

排列的電話簿。

如果我要找:

姓張的人

很好找。

如果我要找:

姓張,而且名字叫小明的人

也很好找。

但如果我只知道:

名字叫小明

就沒辦法直接從「姓氏」這個排序起點快速縮小範圍。

放回 (user_id, status)

WHERE user_id = ?

以及:

WHERE user_id = ?
AND status = ?

都能從最前面的 user_id 開始縮小搜尋範圍。

但如果只有:

WHERE status = ?

這組 Index 通常就沒有前兩種情況那麼直接有效。

這就是 Leftmost Prefix(最左前綴) 最重要的直覺:

Composite Index 裡,排在前面的欄位會影響 Database 能不能有效縮小搜尋範圍。

所以:

(user_id, status)

和:

(status, user_id)

不能直接當成一樣。

順序也不是照 Schema 從左到右抄。

還是得回頭看:

系統真正的 Query Pattern 是什麼?


Index 不是加越多越好

講到這裡,很容易冒出下一個想法:

那乾脆把常查的欄位和組合都加上 Index,不就好了?

問題是,Index 不是免費的。

建立 Index 之後,Database 不只要保存原本的 Table,還要另外保存 Index。

而且資料改變時,Index 也要跟著更新。

例如:

INSERT INTO orders ...

新增一筆訂單時,不是只有 Table 多一筆資料。

相關 Index 也需要一起維護;UPDATEDELETE 也可能帶來額外的 Index 維護成本。

所以 Index 本身就是一種 Trade-off(取捨):

好處
特定查詢可能找得更快

代價
需要額外 Storage
INSERT / UPDATE / DELETE 也要維護 Index

這也是為什麼 Index Design 不是:

能加多少就加多少。

而是:

這支 Query 值不值得用額外的空間和寫入成本,換更好的查找效率?


最後真的替一支 Query 加上 Index

把今天的概念放回一支完整 Query。

假設訂單頁經常需要:

顯示某位使用者已完成的訂單。

SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'completed'
ORDER BY created_at DESC;

前面已經知道,這裡反覆出現的 Query Pattern 是:

user_id + status

所以可以先提出一個 Composite Index:

CREATE INDEX idx_orders_user_status
ON orders(user_id, status);
--                      ↑
--   user_id 在前,status 接在後面

為什麼不是看到 WHERE 就替兩個欄位各建一份 Index?

因為這裡真正想支援的是:

WHERE user_id = ?
AND status = ?

這組反覆出現的 Query Pattern。

(user_id, status) 也保留了從 user_id 開始查找的能力。

到這裡,今天學到的幾件事其實已經一起用上了:

Query Pattern
→ user_id + status

Selectivity
→ 觀察兩個條件各自能縮小多少資料

Composite Index
→ 把經常一起查的欄位放進同一組 Index

Leftmost Prefix
→ 欄位順序不能隨便放

Trade-off
→ 加 Index 也會增加 Storage 與寫入成本

不過這支 Query 還有一行:

ORDER BY created_at DESC;

那是不是乾脆再把 created_at 放進去:

CREATE INDEX idx_orders_user_status_created
ON orders(user_id, status, created_at DESC);

這樣就一定更快?

現在還不能直接下結論。

因為前面做的,其實都是根據 Query Pattern 提出的 Index Design 候選方案

到底:

(user_id, status)

比較好,還是:

(user_id, status, created_at DESC)

比較好?

甚至 Database 最後到底有沒有用到這份 Index?

這時就不能只看 SQL 了,而是要直接看它實際怎麼執行。

這會用到 EXPLAIN / EXPLAIN ANALYZE,也就是我們明天要看的內容。


AI 幫我加好 Index,不就好了嗎?

假設今天我把 orders 的 Schema 丟給 Coding Agent:

「幫我優化訂單查詢的效能。」

如果只把 Table 結構交給 AI,卻沒有提供實際 Query Pattern,它就只能先從 Schema 推測哪些欄位可能需要 Index,例如:

CREATE INDEX idx_orders_user_id
ON orders(user_id);

CREATE INDEX idx_orders_status
ON orders(status);

CREATE INDEX idx_orders_created_at
ON orders(created_at);

看起來三個常用欄位都有 Index,好像很完整。

但真正回頭看系統的 Query,才發現最常跑的是:

WHERE user_id = ?
AND status = ?
ORDER BY created_at DESC;

這時問題就不再是:

「哪些欄位可以加 Index?」

而是:

「這支 Query 真正需要什麼 Index?」

這也是 Vibe Coding 和專業開發比較容易拉開差距的地方。

  • Vibe Coding:很容易停在「AI 已經幫我加了 Index,而且 Code 可以跑」。
  • 專業開發:會再往下確認實際 Query Pattern 是什麼、這些 Index 有沒有重複或多餘、Composite Index 的順序是否符合查詢方式,以及新增的讀取效益值不值得交換寫入與儲存成本。

AI 很快就能寫出 CREATE INDEX

真正需要工程判斷的,是哪些 Index 應該留下。


原來 Database 怎麼設計,也會影響效能

以前想到「效能優化」,我第一個想到的比較像是 Code。

是不是哪段邏輯寫得不好?

是不是演算法還能再優化?

但重新研究 Index 之後,我覺得很神奇的是:

原來可以透過 Database Design 本身,讓資料被找得更有效率。

而且 Index 也不是加上去就結束了。

還要知道系統平常怎麼 Query、哪些條件真的有區分力、Composite Index 怎麼排,以及這份 Index 帶來的成本值不值得。

今天我們已經可以根據 Query,提出一份有理由的 Index Design。

但還差最後一步:

它實際上真的有比較快嗎?

下一篇,就直接打開 EXPLAIN ANALYZE,看看 Database 到底是怎麼跑這支 Query 的。


上一篇
Day27|網站終於有自己的連結了,這樣就算部署完成了嗎?從 Deployment 看懂 Environment 與 CI/CD
下一篇
Day29|Index 加了,Database 真的有在用嗎?從 EXPLAIN ANALYZE 看懂 Query 到底怎麼跑
系列文
夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言